/*
* LearnModule.java 
* 
* Copyright (C) 2009 Aalborg University
*
* contact:
* jaeger@cs.aau.dk   http://www.cs.aau.dk/~jaeger/Primula.html
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

package RBNgui;

import RBNpackage.*;
import RBNinference.*;
import RBNExceptions.*;
import RBNio.*;
import RBNutilities.*;
import RBNLearning.*;

import java.io.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

import myio.StringOps;

import java.util.*;


public class LearnModule extends JFrame implements ActionListener,MouseListener,GradientGraphOptions
{
	
	/**
	 * @uml.property  name="tabbedPane"
	 * @uml.associationEnd  multiplicity="(1 1)"ggrandominit
	 */
	private JTabbedPane tabbedPane   = new JTabbedPane();
	
	/**
	 * @uml.property  name="dataPanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel dataPanel = new JPanel(new GridLayout(2,1));
	
	private JPanel numRelPanel = new JPanel(new GridLayout(1,1));
	
	/**
	 * @uml.property  name="lowerlearnPanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel lowerlearnPanel = new JPanel(new GridLayout(2,1));
	/**
	 * @uml.property  name="learnButtons"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel learnButtons = new JPanel(new FlowLayout());
	/**
	 * @uml.property  name="sampleoptions"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel sampleoptions = new JPanel(new GridLayout(2,1));
	/**
	 * @uml.property  name="samplesizepanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel samplesizepanel = new JPanel(new FlowLayout());
	/**
	 * @uml.property  name="percmisspanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel percmisspanel = new JPanel(new FlowLayout());
	/**
	 * @uml.property  name="restartspanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel restartspanel = new JPanel(new FlowLayout());
	/**
	 * @uml.property  name="datasrcPanel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JPanel datasrcPanel = new JPanel(new FlowLayout());
	
	
	/**
	 * @uml.property  name="fileChooser"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JFileChooser fileChooser = new JFileChooser( "." );
	/**
	 * @uml.property  name="myFilterRDEF"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private javax.swing.filechooser.FileFilter myFilterRDEF;
	


	/**
	 * @uml.property  name="samplesizelabel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JLabel samplesizelabel  = new JLabel("Sample size");
	/**
	 * @uml.property  name="percmisslabel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JLabel percmisslabel  = new JLabel("Percent missing");
	/**
	 * @uml.property  name="restartlabel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JLabel restartlabel  = new JLabel("Restarts");

	
	/**
	 * @uml.property  name="dataFileName"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
//	private JTextField dataFileName        = new JTextField(15);
	/**
	 * @uml.property  name="textsamplesize"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JTextField textsamplesize = new JTextField(15);
	/**
	 * @uml.property  name="textpercmiss"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JTextField textpercmiss = new JTextField(3);
    /**
	 * @uml.property  name="textnumrestarts"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
    private JTextField textnumrestarts = new JTextField(5);
    
	/**
	 * @uml.property  name="loadDataButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
//	private JButton loadDataButton         = new JButton("Load");
	/**
	 * @uml.property  name="sampleDataButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JButton sampleDataButton       = new JButton("Sample");
	/**
	 * @uml.property  name="saveDataButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
//	private JButton saveDataButton         = new JButton("Save");
	/**
	 * @uml.property  name="learnButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JButton learnButton         = new JButton("Learn");
	/**
	 * @uml.property  name="stoplearnButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JButton stoplearnButton         = new JButton("Stop");
	/**
	 * @uml.property  name="setParamButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JButton setParamButton         = new JButton("Set");
	/**
	 * @uml.property  name="learnSettingButton"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JButton learnSettingButton         = new JButton("Settings");
	
	
	
	/**
	 * @uml.property  name="parametertable"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JTable parametertable         = new JTable();
	
	private JList numrellist         = new JList();
	
	private DefaultListModel numRelListModel = new DefaultListModel();
	
	/**
	 * @uml.property  name="parammodel"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private ParameterTableModel parammodel = new ParameterTableModel();
	/**
	 * @uml.property  name="parameterScrollList"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JScrollPane parameterScrollList = new JScrollPane();

	private JScrollPane numRelScrollList = new JScrollPane();
	
	/**
	 * @uml.property  name="learnsplitpane"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private JSplitPane learnsplitpane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,parameterScrollList,lowerlearnPanel);

	/**
	 * @uml.property  name="myprimula"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="learnModule:RBNgui.Primula"
	 */
	private Primula myprimula;
	/**
	 * @uml.property  name="mystaticprimula"
	 * @uml.associationEnd  readOnly="true"
	 */
	private Primula mystaticprimula;
	/**
	 * @uml.property  name="data"
	 * @uml.associationEnd  
	 */
	private RelData data;
	/**
	 * @uml.property  name="datafile"
	 */
//	private File datafile;
	/**
	 * @uml.property  name="settingswindow"
	 * @uml.associationEnd  inverse="learnmodule:RBNgui.SettingsLearn"
	 */
	private SettingsLearn settingswindow;
	
	/**
	 * @uml.property  name="settingswindowopen"
	 */
	private boolean settingswindowopen;
	/* Parameters for the data sampling process */
	/**
	 * @uml.property  name="samplesize"
	 */
	private int samplesize;
	/**
	 * @uml.property  name="percmiss"
	 */
	private double percmiss;
	/* Parameters for the learning process */
	/**
	 * @uml.property  name="numchains"
	 */
	private int numchains;
	/**
	 * @uml.property  name="restarts"
	 */
	private int restarts;
	
	/* Percentage by which to sub-sample false atoms */
	private int subsamples;
	
	/* Number of parameter blocks for block gradient descent */
	private int numblocks;
	
	/**
	 * @uml.property  name="windowsize"
	 */
	private int windowsize;
	/**
	 * @uml.property  name="maxfails"
	 */
	private int maxfails;
	/**
	 * @uml.property  name="maxiterations"
	 */
	private int maxiterations;
	/**
	 * @uml.property  name="linedistancethresh"
	 */
	private double linedistancethresh;
	/**
	 * @uml.property  name="linelikelihoodthresh"
	 */
	private double linelikelihoodthresh;
	/**
	 * @uml.property  name="gradientdistancethresh"
	 */
	private double gradientdistancethresh;
	/**
	 * @uml.property  name="paramratiothresh"
	 */
	private double paramratiothresh;
	/**
	 * @uml.property  name="omitrounds"
	 */
	private int omitrounds;
	/**
	 * @uml.property  name="verbose"
	 */
	private boolean ggverbose;
	private boolean ggrandominit;
	
	/**
	 * @uml.property  name="aca"
	 */
	private boolean aca;
	
	/**
	 * @uml.property  name="lthread"
	 * @uml.associationEnd  
	 */
	LearnThread lthread;
	
	private Vector<String> selectednumrels;
	
	public LearnModule(Primula mypr){

		myprimula = mypr;
		data = mypr.getReldata();
//		datafile = null;
		settingswindowopen = false;
		samplesize = 1;
		restarts = -1; /*-1 is for open-ended restarts */
		subsamples = 100;
		numblocks = 1;
		numchains = 2;
		windowsize = 2;
		maxfails = 5;
		maxiterations = 30;
		linedistancethresh = 0.01;
		linelikelihoodthresh = 0.001;
		gradientdistancethresh = 0.001;
		paramratiothresh = 0.03;
		omitrounds = 1;
		percmiss = 0.0;
		ggverbose = false;
		ggrandominit = true;
		aca = false;
		readNumRels();
		selectednumrels = new Vector<String>();
		
		fileChooser.addChoosableFileFilter(myFilterRDEF = new Filter_rdef());

		/* Data Tab */
		/* Data File Load */
//		loadDataButton.addActionListener(this);
//		loadDataButton.setBackground(Primula.COLOR_BLUE);
		sampleDataButton.addActionListener(this);
		sampleDataButton.setBackground(Primula.COLOR_RED);
//		saveDataButton.addActionListener(this);
//		saveDataButton.setBackground(Primula.COLOR_GREEN);
		textsamplesize.addActionListener(this);
		textpercmiss.addActionListener(this);
		
//		datasrcPanel.add(loadDataButton);
		datasrcPanel.add(sampleDataButton);
//		datasrcPanel.add(saveDataButton);

		samplesizepanel.add(samplesizelabel);
		samplesizepanel.add(textsamplesize);
		percmisspanel.add(percmisslabel);
		percmisspanel.add(textpercmiss);

		sampleoptions.setBorder(BorderFactory.createTitledBorder("Sampling Options"));
		textsamplesize.setText("" + samplesize);
		textpercmiss.setText("" + percmiss);
		
		
		dataPanel.add(datasrcPanel);
		sampleoptions.add(samplesizepanel);
		sampleoptions.add(percmisspanel);
		dataPanel.add(sampleoptions);
		
		/* Num Rel Tab*/		
		numrellist.addMouseListener( this );
		numrellist.setModel(numRelListModel);
		numrellist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		numRelScrollList.getViewport().add(numrellist);
		numRelPanel.add(numRelScrollList);
		
		/* Learn Tab */
		/* Parameter Table */
		parametertable.setModel(parammodel);
	  	parametertable.getColumnModel().getColumn(0).setHeaderValue("Parameter");
	  	parametertable.getColumnModel().getColumn(1).setHeaderValue("Value");
	  	parameterScrollList.getViewport().add(parametertable);
		parameterScrollList.setMinimumSize(new Dimension(0,100));
		learnButton.addActionListener(this);
	  	learnButton.setBackground(Primula.COLOR_GREEN);
	  	stoplearnButton.addActionListener(this);
	  	stoplearnButton.setBackground(Primula.COLOR_RED);
	  	setParamButton.addActionListener(this);
	  	setParamButton.setBackground(Primula.COLOR_YELLOW);
	  	learnSettingButton.addActionListener(this);
	  	learnSettingButton.setBackground(Primula.COLOR_BLUE);
	  	learnButtons.add(learnButton);
	  	learnButtons.add(stoplearnButton);
	  	learnButtons.add(setParamButton);
	  	learnButtons.add(learnSettingButton);
	  	
	  	restartspanel.add(restartlabel);
	  	restartspanel.add(textnumrestarts);
	  	
		//learnPanel.add(parameterScrollList);
		lowerlearnPanel.add(learnButtons);
		lowerlearnPanel.add(restartspanel);

		/* Loading parameters into table! */
		//parammodel.setParameters(mypr.getRBN().parameters());
		
		
		/* Main Pane */
		
		
		tabbedPane.add("Learning",learnsplitpane);
		tabbedPane.add("Relation Parameters", numRelPanel);
		tabbedPane.add("Sample Data",dataPanel);
		
		//Inner class for closing the window
		this.addWindowListener(
				new WindowAdapter(){
					public void windowClosing(WindowEvent e){
						dispose();
						Primula.setIsLearnModuleOpen(false);
					}
				}
		);


		Container contentPane = this.getContentPane();
		contentPane.setLayout(new BoxLayout(contentPane,BoxLayout.Y_AXIS));
		contentPane.add(tabbedPane);
		
		
		
		ImageIcon icon = myprimula.getIcon( Primula.STR_FILENAME_LOGO );
		if (icon.getImageLoadStatus() == MediaTracker.COMPLETE) //image ok
			this.setIconImage(icon.getImage());
		this.setTitle("Learn Module");
		this.setSize(400, 300);
		this.setVisible(true);

	}
	
	public void actionPerformed( ActionEvent e ) {
		Object source = e.getSource();	

		if (source == sampleDataButton){
			if (!myprimula.getReldata().hasProbData() || confirm("Unsaved sampled data or evidence will be lost. Continue?")){				
				Sampler sampl = new Sampler();
				sampl.makeSampleStruc(myprimula);
				mystaticprimula.showMessage("Sampling ... 0% ");
				data = new RelData();
				RelDataForOneInput dataforinput = new RelDataForOneInput(myprimula.getRels());
				int completion = 0;
				for (int i=0;i<samplesize;i++){
					dataforinput.addCase(sampl.sampleOneStrucData(percmiss));
					if (10*i/samplesize>completion){
						mystaticprimula.appendMessage("X");
						completion++;
					}					
				}
				data.add(dataforinput);
				mystaticprimula.appendMessage("100%");
				myprimula.setRelData(data);
				myprimula.getInstFromReldata();
				myprimula.updateBavaria();
				myprimula.updateInstantiationInEM();
			}
		}	


		if (source == learnButton){
			RelData learndata = data;
			if (this.getSubsamples()<100){
				learndata = data.subSampleData(this.getSubsamples());
			}

			
			lthread = new LearnThread(myprimula, 
					learndata, 
					parammodel, 
					parametertable,
					textnumrestarts,
					this);
			lthread.start(); 
		}
		if (source == stoplearnButton){
			lthread.setStopped();
		}
		if (source == setParamButton){
			String[] pars = parammodel.getParameters();
			double[] vals = parammodel.getEstimates();
			/* Need to cut off last element, which is the likelihood: */
			myprimula.setParameters(Arrays.copyOfRange(pars,0,pars.length-1),
					         		Arrays.copyOfRange(vals,0,vals.length-1));
		}
		
		if (source == learnSettingButton){
			if (!settingswindowopen){
				settingswindow = new RBNgui.SettingsLearn(this);
				settingswindowopen = true;
			}
		}

		if( source == textsamplesize ){
			try{
				samplesize = new Integer(textsamplesize.getText());
			}
			catch(NumberFormatException exception){
			}
		}
		else if( source == textpercmiss ){
			try{
				percmiss = new Double(textpercmiss.getText());
			}
			catch(NumberFormatException exception){
			}
		}
	}
	
    //  Invoked when the mouse button has been clicked (pressed and released) on a component.
	public void mouseClicked(MouseEvent e) {
		Object source = e.getSource();
	}

//  Invoked when the mouse enters a component.
	public void mouseEntered(MouseEvent e) {
		Object source = e.getSource();
	}
	
	public void mouseExited(MouseEvent e) {
		Object source = e.getSource();
	}
	//           Invoked when a mouse button has been pressed on a component.
	public void mousePressed(MouseEvent e) {
		Object source = e.getSource();
		if(source == numrellist){
			int index = numrellist.locationToIndex(e.getPoint());
			if(index >= 0){;
				if (numrellist.isSelectedIndex(index)){
					numrellist.removeSelectionInterval(index,index);
				}
				else{
					numrellist.addSelectionInterval(index,index);
				}
			}
			
			System.out.println("selection: " + StringOps.arrayToString(numrellist.getSelectedIndices(), "[", "]") + "   " 
					+ StringOps.arrayToString(getSelectedNumRels(), "[", "]"));
			
	
		}
	}


	//          Invoked when a mouse button has been pressed on a component.
	public void mouseReleased(MouseEvent e) {
		Object source = e.getSource();
	}
	//          Invoked when a mouse button has been released on a component.



	
	
	public void setLearnSampleSize(Integer lss){
		numchains = lss;
	}
	public void setWindowSize(Integer gr){
		windowsize = gr;
	}

	public void setMaxIterations(Integer mi){
		maxiterations = mi;
	}
	
	public void setMaxFails(Integer mf){
		maxfails = mf;
	}

	public void setLineDistThresh(double d){
		linedistancethresh = d;
	}

	public void setLineLikThresh(double d){
		linelikelihoodthresh = d;
	}

	public void setGradDistThresh(double d){
		gradientdistancethresh = d;
	}

	
	public int getMaxIterations(){
		return maxiterations;
	}
	
	public int getMaxFails(){
		return maxfails;
	}

	public double getLineDistThresh(){
		return linedistancethresh;
	}

	public double getLineLikThresh(){
		return linelikelihoodthresh;
	}

	public double getGradDistThresh(){
		return gradientdistancethresh;
	}

	
	public int getWindowSize(){
		return windowsize;
	}

	public void setRestarts(Integer rs){
		restarts = rs;
	}

	public void setSubsamples(Integer rs){
		subsamples = rs;
	}

	public void setNumblocks(Integer nb){
		numblocks = nb;
	}

	/**
	 * @return
	 * @uml.property  name="restarts"
	 */
	public int getRestarts(){
		return restarts;
	}
	
	public int getSubsamples(){
		return subsamples;
	}
	
	public int getNumblocks(){
		return numblocks;
	}
	
	
	
	
	public void setSettingsOpen(boolean b){
		settingswindowopen = b;
	}
	
	public int getNumChains(){
		return numchains;
	}
	
	public String[] getSelectedNumRels(){
		int[] selindices = numrellist.getSelectedIndices();
		String[] result = new String[selindices.length];
		for (int i=0;i<result.length;i++)
			result[i]=(String)numRelListModel.elementAt(selindices[i]);
		return result;
	}
	
	/**
	 * @param v
	 * @uml.property  name="verbose"
	 */
	public void setVerbose(boolean v){
		ggverbose = v;
	}
	
	public void setRandomInit(boolean v){
		ggrandominit = v;
	}
	
	public boolean ggverbose(){
		return ggverbose;
	}
	
	public boolean ggrandominit(){
		return ggrandominit;
	}
	
	/**
	 * @param v
	 * @uml.property  name="aca"
	 */
	public void setAca(boolean v){
		aca = v;
	}
	
	public boolean aca(){
		return aca;
	}
	
	
	/**
	 * @return
	 * @uml.property  name="paramratiothresh"
	 */
	public double getParamratiothresh(){
		return paramratiothresh;
	}
	
	public int omitRounds(){
		return omitrounds;
	}
	
	public void getRelDataFromPrimula(){
		data=myprimula.getReldata();
	}
	
	public void disableDataTab(){
		tabbedPane.setSelectedIndex(tabbedPane.indexOfComponent(learnsplitpane));
		tabbedPane.setEnabledAt(tabbedPane.indexOfComponent(dataPanel),false);
	}
	
	public void setParameters(String[] params){
		parammodel.setParameters(params);
		parametertable.updateUI();
	}
	
	public void setParameterValues(double[] pvals){
		parammodel.setParameterEstimates(pvals);
		parametertable.updateUI();
	}
	
	private void readNumRels(){
		if(myprimula.rels instanceof SparseRelStruc){
			SparseRelStruc sparserst = (SparseRelStruc)myprimula.rels;
			Vector<NumRel> nextrels;
			nextrels = sparserst.getNumGlobals();
			for (int i=0;i<nextrels.size();i++)
				numRelListModel.addElement(nextrels.elementAt(i).name());
			
			
			nextrels = sparserst.getNumAttributes();
			for (int i=0;i<nextrels.size();i++)
				numRelListModel.addElement(nextrels.elementAt(i).name());
			
			nextrels = sparserst.getNumBinaryRelations();
			for (int i=0;i<nextrels.size();i++)
				numRelListModel.addElement(nextrels.elementAt(i).name());
			
			nextrels = sparserst.getNumArbitraryRelations();
			for (int i=0;i<nextrels.size();i++)
				numRelListModel.addElement(nextrels.elementAt(i).name());
			}
			
		}
	
	
	public boolean confirm(String text){
		int result = JOptionPane.showConfirmDialog(this, text, "Confirmation", JOptionPane.YES_NO_OPTION);
		if (result == JOptionPane.YES_OPTION)
			return true;
		else //result == JOptionPane.NO_OPTION
			return false;
	}
	
	/* Transforms the data as follows:
	 * For all (Boolean) probabilistic relations with default value 'false'
	 * Default value is turned into '?', and pc% of the atoms that had
	 * 'false' values according to the default are explicitly added as 
	 * 'false' 
	 */
	private void subSampleData(int pc){
		data.subSampleData(pc);
	}
}
